#include "common.hpp"

#include "en.hpp"
#include "path.h"

#include <iostream>
#include <string.h>
#include "physics.hpp"

using namespace std;

bool operator==(const vec2& lhs, const vec2& rhs)
{
    return lhs.x == rhs.x && lhs.y == rhs.y;
}

vector<string> peon::trait_list = {"damage", "hunger", "fatigue"};

peon::peon()
{
    for(auto& i : trait_list)
    {
        needs.push_back(false);
    }
}

void peon::path()
{
    if(job_assigned && task!=NULL)
    {
        if(en::collision_win[(int)x][(int)y] == NON_PATHABLE)
        {
            return;
        }

        int path_lookahead = 5;

        int path_lookahead_end = mn + path_lookahead;

        if(path_lookahead_end >= pth.size())
            path_lookahead_end = pth.size()-1;

        bool any_obstruction = false;

        for(int i=mn; i<path_lookahead_end; i++)
        {
            vec2 cur = pth[i];

            any_obstruction |= en::collision_win[cur.x][cur.y] == NON_PATHABLE;
        }

        if(any_obstruction || task->x!=oex || task->y!=oey)
        {
            if(job_assigned && task->b!=NULL)
            {
                pair<int, int> p = task->b->find_nearest_free_space();

                task->x = p.first;
                task->y = p.second;

                if(task->x == -1 || task->y == -1)
                {
                    task->b->pskip++;

                    job_cancel();

                    return;
                }
            }

            pth = pathfind_2(x, y, task->x, task->y, en::collision_win, *this);

            if(pth.size()==0 && job_assigned && task!=NULL)
            {
                if(task->b!=NULL)
                    task->b->pskip++;

                sf::RectangleShape s;
                s.setSize({14, 14});
                s.setFillColor(sf::Color(0, 0, 255));
                s.setPosition(task->x, task->y);
                en::win.draw(s);

                cout << "error: no path to " << task->x << " " << task->y << endl;

                job_cancel();
                return;
            }

            pstate = en::state;
            mn = 0;
            oex = task->x;
            oey = task->y;
        }
    }
}

void peon::push()
{
    auto a = new peon;
    *a = *this;
    en::peons.push_back(a);
    p = a;
}

void peon::pull()
{
    int i = -1;
    for(i=0; i<(int)en::peons.size(); i++) if(en::peons[i]==p) break;
    if(i>=0)
    {
        delete en::peons[i];
        auto it = en::peons.begin();
        advance(it, i);
        en::peons.erase(it);
    }
}

void peon::move(float frametime)
{
    if(pth.size()>mn)
    {
        x = pth[mn].x;
        y = pth[mn].y;
        mn++;
    }
}

///write a peon accept thing, ie can/will they accept job

void peon::job_process()
{
    if(!job_assigned)
        return;

    float job_dist = 6;
    float xd = x - task->x;
    float yd = y - task->y;

    float dist = sqrt(yd*yd + xd*xd);

    if(dist < job_dist)
    {
        task->ticks_to_complete--;
        if(task->block_trait_id!=-1)
            task->b->notify_job_doing(task->block_trait_id);
    }
    if(task->ticks_to_complete<=0)
    {
        if(task->block_trait_id!=-1)
            task->b->notify_job_done(task->block_trait_id);

        job_assigned = false;

        int t_id = task->peon_trait_id;
        if(t_id!=-1)
        {
            traits[t_id].second = 0;
        }

        task = NULL;
    }
}

void peon::tick()
{
    job_process();

    std::string f = "fatigue";

    int pos1 = -1;
    en::contains_trait(trait_list, f, &pos1);

    float& fat = traits[pos1].second;

    f = "hunger";

    en::contains_trait(trait_list, f, &pos1);

    float& hun = traits[pos1].second;

    fat += 0.01f;
    hun += 0.02f;

    needy = false;

    for(int i=0; i<traits.size(); i++)
    {
        needs[i] = false;
        if( traits[i].second > 20 && traits[i].first == "damage")
        {
            needs[i] = true;
            needy = true;
        }
        else if(traits[i].second > 35 && traits[i].first == "fatigue")
        {
            needs[i] = true;
            needy = true;
        }
        else if(traits[i].second > 40 && traits[i].first == "hunger")
        {
            needs[i] = true;
            needy = true;
        }
    }
}

void peon::job_cancel()
{
    if(task->block_trait_id!=-1)
    {
        task->b->notify_job_stopped(task->block_trait_id);
    }

    job_assigned = false;
    en::add_job(task);

    task = NULL;
}

///make this a bit faster
bool peon::should_accept(std::shared_ptr<job> j)
{
    int p_id = j->peon_trait_id;

    if(!needy && p_id == -1)
        return true;

    if(p_id != -1 && needs[p_id])
        return true;

    return false;
}

void block::push()
{
    int w = en::art_t[art_num].getSize().x;
    int h = en::art_t[art_num].getSize().y;

    int xe, ye;

    xe = x + w;
    ye = y + h;

    if(x < 0)
        return;
    if(y < 0)
        return;

    if(xe >= en::w)
        return;
    if(ye >= en::h)
        return;

    std::string over = "overlaps";

    if(!en::contains_trait(type_id, over))
    {
        for(int i=x; i<xe; i++)
        {
            for(int j=y; j<ye; j++)
            {
                if(en::collision_win[i][j]==NON_PATHABLE)
                    return;
            }
        }

        for(int i=x; i<xe; i++)
        {
            for(int j=y; j<ye; j++)
            {
                en::collision_win[i][j] = NON_PATHABLE;
            }
        }
    }


    auto a = new block;
    *a = *this;
    en::blocks.push_back(a);
    b = a;
    //en::state++;
}

void block::pull()
{
    int i = -1;
    for(i=0; i<(int)en::blocks.size(); i++) if(en::blocks[i]==b) break;
    if(i>=0)
    {
        auto it = en::blocks.begin();
        advance(it, i);
        en::blocks.erase(it);
    }
}

pair<int, int> block::find_nearest_free_space()
{
    int ax = x - 1;
    int ay = y - 1;
    int bx = x + en::art_t[art_num].getSize().x + 1;
    int by = y + en::art_t[art_num].getSize().y + 1;

    if(ax < 0)
        ax = 0;
    if(ax >= en::w)
        ax = en::w - 1;

    if(ay < 0)
        ay = 0;
    if(ay >= en::h)
        ay = en::h - 1;

    if(bx < 0)
        bx = 0;
    if(bx >= en::w)
        bx = en::w - 1;

    if(by < 0)
        by = 0;
    if(by >= en::h)
        by = en::h - 1;

    if(pskip >= 4)
        pskip = 0;


    int midx = ax + bx;
    midx/=2;

    int midy = ay + by;
    midy/=2;

    if(midx < 0)
        midx = 0;
    if(midx >= en::w)
        midx = en::w - 1;

    if(midy < 0)
        midy = 0;
    if(midy >= en::h)
        midy = en::h - 1;

    if(en::collision_win[midx][ay] == PATHABLE && pskip < 1)
    {
        return {midx, ay};
    }
    if(en::collision_win[midx][by] == PATHABLE && pskip < 2)
    {
        return {midx, by};
    }
    if(en::collision_win[ax][midy] == PATHABLE && pskip < 3)
    {
        return {ax, midy};
    }
    if(en::collision_win[bx][midy] == PATHABLE && pskip < 4)
    {
        return {bx, midy};
    }

    return {-1, -1};
}

void square::push()
{
    en::squares.push_back(*this);
    en::state++;
}

bool operator<(const job& lhs, const job& rhs)
{
    return lhs.priority < rhs.priority;
}
bool o_more(std::shared_ptr<job> j1, std::shared_ptr<job> j2)
{
    return j1->priority > j2->priority;
}

std::shared_ptr<job> job::push()
{
    std::shared_ptr<job> j = std::make_shared<job>(*this);
    if(peon_trait_id==-1)
    {
        en::add_job(j);
        return en::jobs.back();
    }
    else
    {
        en::satisfying_peon_jobs[peon_trait_id].push_back(j);
        return en::satisfying_peon_jobs[peon_trait_id].back();
    }
}

///logic for this not 100%
void job::cancel()
{
    int trait_id = p->task->block_trait_id;;
    p->job_assigned = false;
    p->task = NULL;

    b->jobs[trait_id] = NULL;

    b->jobs_doing[trait_id] = false;
    b->jobs_going[trait_id] = false;
}

job::job()
{

}

job::job(int px, int py, float prior, block* pb, int pblock_trait_id, int ppeon_trait_id, int time)
{
    x = px;
    y = py;
    priority = prior;
    b = pb;
    block_trait_id = pblock_trait_id;
    peon_trait_id = ppeon_trait_id;
    ticks_to_complete = time;
}

std::string& get_trait(int b_id, int t_id)
{
    auto& traits = en::trait_list[b_id];
    return traits[t_id].first;
}

void process_job_trait(int trait_id, block* b, float& priority_modifier)
{
    int b_type = b->type_id;
    auto& traits = en::trait_list[b_type];

    string trait = traits[trait_id].first;
    float max_val = traits[trait_id].second;
    float cur_val = b->trait_nums[trait_id];

    int ox, oy;

    pair<int, int> point;

    point = b->find_nearest_free_space();

    if(point.first == -1 || point.second == -1)
        return;

    ox = point.first;
    oy = point.second;

    if(b->jobs_going[trait_id])
        return;

    bool gen_job = false;
    int p_id = -1;
    float priority = 1;

    int ticks = 1;

    //if(trait == "explodes")
    //{
    //    priority_modifier += cur_val;
    //    return;
    //}


    if(strncmp(trait.c_str(), "provides_", 9)==0)
    {
        std::string trait_provided = &trait.c_str()[9];

        std::string work_id_string = std::string("work_") + trait_provided;

        int tr_id = en::find_trait(traits, work_id_string);

        bool currently_working = b->jobs_doing[tr_id];

        if(currently_working)
        {
            ///theres currently a job going for work_thing, which doesnt necessarily mean it currently actually being worked
            ///but close enough i guess
        }
        else
        {
            return;
        }

        trait = trait_provided;

    }

    if(trait == "health" && cur_val/max_val > 0.2) ///health is actually damage taken
    {
        std::string rep = "repairable";
        float r_val = en::get_val(traits, rep);

        ticks = ceilf(cur_val/r_val);

        priority = (cur_val/max_val) * 10.0f;

        gen_job = true;
    }
    else if(trait == "fatigue")
    {
        ticks = 100;
        priority = 8;

        gen_job = true;

        int pos;

        std::string t = "fatigue";

        bool d = en::contains_trait(peon::trait_list, t, &pos);

        if(!d)
            return;

        p_id = pos;
    }
    else if(trait == "damage")
    {
        ticks = 100;
        priority = 20;

        gen_job = true;

        int pos;

        std::string t = "damage";

        bool d = en::contains_trait(peon::trait_list, t, &pos);

        if(!d)
            return;

        p_id = pos;
    }
    else if(trait == "hunger")
    {
        ticks = 100;
        priority = 20;

        gen_job = true;

        int pos;

        std::string t = "hunger";

        bool d = en::contains_trait(peon::trait_list, t, &pos);

        if(!d)
            return;

        p_id = pos;
    }
    else if(trait == "air")
    {
        if(dens_prev==NULL)
            return;

        int x = b->x-1;
        int y = b->y-1;

        int ex = en::art_t[b->art_num].getSize().x + 2 + x;
        int ey = en::art_t[b->art_num].getSize().y + 2 + y;

        int epx = ex / en::map_const_x;
        int epy = ey / en::map_const_y;

        int px, py;
        px = x / en::map_const_x;
        py = y / en::map_const_y;

        int mpx = (px + epx) / 2;
        int mpy = (py + epy) / 2;

        px-=1;
        py-=1;
        epx+=1;
        epy+=1;

        for(int p=0; p<8; p++)
        {
            int i, j;

            switch (p)
            {
            case 0:
                i = px, j=py;
                break;
            case 1:
                i = mpx, j=py;
                break;
            case 2:
                i = epx, j=py;
                break;
            case 3:
                i = px, j=mpy;
                break;
            case 4:
                i = epx, j=mpy;
                break;
            case 5:
                i = px, j=epy;
                break;
            case 6:
                i = mpx, j=epy;
                break;
            case 7:
                i = epx, j=epy;
                break;
            }

            float xvec = mpx - i;
            float yvec = mpy - j;

            dens_prev[i + c_map_x*j] = 0.05f;

            u_prev[i + c_map_x*j] = -xvec*0.1/60.0f;
            v_prev[i + c_map_x*j] = -yvec*0.1/60.0f;

        }


        //int midx = (x + ex)/2.0f;
        //int midy = (y + ey)/2.0f;


    }
    else if(strncmp(trait.c_str(), "work_", 5)==0)
    {
        ticks = 300;
        priority = 5;

        gen_job = true;
    }

    if(gen_job)
    {
        b->jobs[trait_id] = NULL;
        b->jobs_going[trait_id] = true;
        job j(ox, oy, priority*priority_modifier, b, trait_id, p_id, ticks);
        b->jobs[trait_id] = j.push();
    }
}

void block::tick()
{
    float p_mod = 1;
    for(int i=0; i<trait_nums.size(); i++)
    {
        process_job_trait(i, this, p_mod);
    }
}

void block::init(int type)
{
    int siz = en::trait_list[type].size();
    for(int i=0; i<siz; i++)
    {
        jobs_going.push_back(false);
        jobs_doing.push_back(false);
        jobs.push_back(NULL);
        trait_nums.push_back(0.0f);
    }
}

void block::notify_job_done(int trait_id)
{
    jobs_going[trait_id] = false;
    jobs_doing[trait_id] = false;

    if(get_trait(type_id, trait_id) == "health" || get_trait(type_id, trait_id) == "process_health")
    {
        trait_nums[trait_id] = 0;
    }

    jobs[trait_id] = NULL;
}

void block::notify_job_doing(int trait_id)
{
    jobs_doing[trait_id] = true;
}

void block::notify_job_stopped(int trait_id)
{
    jobs_doing[trait_id] = false;

    jobs[trait_id] = NULL;
}
